local cache = require "kong.plugins.rbactest.cache"
local BasePlugin = require "kong.plugins.base_plugin"
local basic_serializer = require "kong.plugins.kong-splunk-log.basic"
local kong = kong
local cjson = require "cjson"
local http = require "resty.http"
local cjson_encode = cjson.encode
local concat = table.concat
local timer_at = ngx.timer.at
local RbacHandler = BasePlugin:extend()

RbacHandler.PRIORITY = 1005
RbacHandler.VERSION = "1.0.0"

--[[
  ** get_request_key: 从请求中获取http_method及uri, 获取唯一权限key值. 权限key格式： httpmethod_uri(转换成标准格式):
   system_path_list: conf中的配置
]] --
-- TODO
--统一的返回方法
local function rbac_response(status_code, code, msg)
    local res_body = {
        ["msg"] = msg,
        ["code"] = code
    }
    kong.response.exit(status_code, res_body)
end
--[[
 ** get_user_info: 根据请求中的Authorization从redis中获取用户id （userId）
]] --
local function get_user_id(red, conf)
    local token = kong.request.get_header("Authorization")
    if token then
        local cache_user_info = cache.get_authorization_user_info(token, red)
        if cache_user_info and tostring(cache_user_info) ~= "userdata: NULL" then
            local user_info = cjson.decode(cache_user_info)
            if user_info ~= nil then
                local user_id = user_info["tenantId"]
                if user_id then
                    return user_id
                end
            end
        end
    end
    kong.log.err(
            "rbac->认证鉴权失败(Authorization token header中不正确 or token对应的tenantId 不正确!)")
    cache.close_redis(red)
    rbac_response(conf.identify_status_code, conf.identify_msg_code, conf.identify_msg)
end

-- 判断是否可以调用该接口
-- 通过请求校验接口（identify_url）来获取当前调用是否可以通过 参数是{"header":"","request":""}
-- 这种方式由于每次都需要http请求额外的校验接口，会影响接口的整体响应速度。
local function checkAble(conf)
    local json_str = cjson.decode('{"request": "","header":""}')
    json_str['header'] = cjson_encode(kong.request.get_headers())
    json_str['request'] = cjson_encode(basic_serializer.serialize(ngx))
    local identifyUrl = conf.identify_url
    local jsonStrEncode = cjson.encode(json_str)
    kong.log.err(
            " able json",jsonStrEncode)
    local httpc = http.new()
    local res, err = httpc:request_uri(
            identifyUrl,
            {
                method = "POST",
                body = jsonStrEncode,
                headers = {
                    ["Content-Type"] = "application/json"
                }
            }
    )
    -- 返回
    if not res then
        kong.log.err("failed get able result", err)
    else
        local resJson = cjson.decode(res.body)
        --if not true 则 返回请求
        if not resJson.data then
            rbac_response(conf.identify_msg_code, tonumber(resJson.code), resJson.msg)
        end
    end
end

local function send_payload(premature, conf, payload)
    if premature then
        return
    end
    local method = conf.http_method
    local timeout = conf.http_timeout
    local keepalive = conf.http_keepalive
    local content_type = conf.http_content_type
    local parsed_url = conf.http_parsed_url
    local host = conf.http_host
    local port = conf.http_port
    local httpc = http.new()
    httpc:set_timeout(timeout)
    local ok, err = httpc:connect({
        scheme = "http",
        host = host,
        port = port,
    })
    if not ok then
        ngx.log(ngx.ERR, "connection failed: ", err)
        return
    end

    if parsed_url.scheme == "https" then
        local _, err = httpc:ssl_handshake(true, parsed_url, false)
        if err then
            return nil, "failed to do SSL handshake with " ..
                    parsed_url .. ":" .. tostring(port) .. ": " .. err
        end
    end

    local res, err = httpc:request({
        method = method,
        path = parsed_url,
        headers = {
            ["Host"] = host,
            ["Content-Type"] = content_type,
            ["Content-Length"] = #payload,
        },
        body = payload,
    })
    if not res then
        return nil, "failed request to " .. host .. ":" .. tostring(port) .. ": " .. err
    end

    -- always read response body, even if we discard it without using it on success
    local response_body = res:read_body()
    local success = res.status < 400
    local err_msg

    if not success then
        err_msg = "request to " .. host .. ":" .. tostring(port) ..
                " returned status code " .. tostring(res.status) .. " and body " ..
                response_body
    end
    if success then
        err_msg = response_body
    end
    ok, err = httpc:set_keepalive(keepalive)
    if not ok then
        -- the batch might already be processed at this point, so not being able to set the keepalive
        -- will not return false (the batch might not need to be reprocessed)
        kong.log.err("failed keepalive for ", host, ":", tostring(port), ": ", err)
    end
    return success, err_msg
end

--[[
  插件逻辑实现
  access 收到客户端请求,转向上流服务之前执行
  header_filter()	上流服务处理完成后,返回给客户端之前
  body_filter()	上流服务处理完成后,返回给客户端之前
  log()	返回给客户端完成后执行
]]
--一次请求对应一次
function RbacHandler:access(conf)
    RbacHandler.super.access(self)
    local allowIdentify = conf.allow_identify
    if allowIdentify == true then
        checkAble(conf)
    end
end
--一次请求对应两次对从上游服务接收到的响应主体的每个块执行。由于响应被流回客户端，因此它可能超出缓冲区大小，并且逐块流式传输。因此，如果响应较大，则可以多次调用此方法。有关更多详细信息，请参见lua-nginx-module文档。
function RbacHandler:body_filter()
    RbacHandler.super.body_filter(self)
    kong.log.err("请求的body_filter")
    local ctx = ngx.ctx
    local chunk, eof = ngx.arg[1], ngx.arg[2]
    local uncompress
    ctx.rt_body_chunks = ctx.rt_body_chunks or {}
    ctx.rt_body_chunk_number = ctx.rt_body_chunk_number or 1

    if eof then
        local chunks = concat(ctx.rt_body_chunks)

        local encoding = kong.response.get_header("Content-Encoding")
        if encoding == "gzip" then
            uncompress = zlib.inflate()(chunks)
        end

        kong.ctx.plugin.respbody = uncompress or chunks
        ngx.arg[1] = chunks
    else
        ctx.rt_body_chunks[ctx.rt_body_chunk_number] = chunk
        ctx.rt_body_chunk_number = ctx.rt_body_chunk_number + 1
        ngx.arg[1] = nil
    end
    --service body is only available with buffered proxying
end

function RbacHandler:log(conf)
    RbacHandler.super.log(self)
    local allowHttplog = conf.allow_http_log
    if allowHttplog == true then
        local json_str = cjson.decode('{"request": "", "response": "","header":""}')
        local body = kong.ctx.plugin.respbody
        json_str['header'] = cjson_encode(kong.request.get_headers())
        json_str['request'] = cjson_encode(basic_serializer.serialize(ngx))
        json_str['response'] = body
        local ok, err = timer_at(0, send_payload, conf, cjson.encode(json_str))
        if not ok then
            kong.log.err("failed to get log: ", err)
        end
    end

end

return RbacHandler
